// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library PegParser;

/*
 * The following functions are combinators for building Rules.
 *
 * A rule is one of the following
 * - A String which matches the string literally.
 * - A Symbol which matches the symbol's definition.
 * - A list of rules with an optional reducing function, which matches a sequence.
 * - The result of calling one of the combinators.
 *
 * Some rules are 'value-generating' rules, they return an 'abstract syntax
 * tree' with the match.  If a rule is not value-generating [:null:] is the
 * value.
 *
 * A Symbol is always a value-generating rule. If the value is not required, use
 * [:SKIP(aSymbol):] in place of [:aSymbol:].
 *
 * A String is not a value-generating rule but can be converted into one by
 * using [:TEXT('string'):] in place of [:'string':].
 *
 * A list or sequence is value-generating depending on the subrules.  The
 * sequence is value-generating if any of the subrules are value-generating or
 * if there is a reducing function.  If no reducing function is given, the value
 * returned depends on the number of value-generating subrules.  If there is
 * only one value generating subrule, that provideds the value for the sequence.
 * If there are more, then the value is a list of the values of the
 * value-generating subrules.
 */

/**
 * Matches one character by a predicate on the character code.
 * If [spec] is an int, that character is matched.
 * If [spec] is a function it is used
 *
 * Example [: CHARCODE((code) => 48 <= code && code <= 57) :] recognizes an
 * ASCII digit.
 *
 * CHARCODE does not generate a value.
 */
_Rule CHARCODE(spec, [name]) {
  if (spec is int)
    return new _CharCodeRule((code) => code == spec, name);
  else
    return new _CharCodeRule(spec, name);
}

/**
 * Matches one of the [characters].
 *
 * CHAR does not generate a value.
 */
_Rule CHAR([characters]) {
  if (characters == null) return const _AnyCharRule();
  if (characters is int) return CHARCODE(characters);

  // Find the range of character codes and construct an array of flags for codes
  // within the range.
  List<int> codes = characters.codeUnits.toList();
  codes.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
  int lo = codes[0];
  int hi = codes[codes.length - 1];
  if (lo == hi) return CHARCODE(lo);
  int len = hi - lo + 1;
  var flags = new List<bool>(len);
  for (int i = 0; i < len; ++i) flags[i] = false;
  for (int code in codes) flags[code - lo] = true;

  return CHARCODE((code) => code >= lo && code <= hi && flags[code - lo]);
}

/**
 * Matches the end of the input.
 *
 * END does not generate a value.
 */
_Rule get END => new _EndOfInputRule();

/**
 * Throws an exception.
 */
_Rule ERROR(String message) => new _ErrorRule(message);

/**
 * Matches [rule] but does not consume the input.  Useful for matching a right
 * context.
 *
 * AT does not generate a value.
 */
_Rule AT(rule) => new _ContextRule(_compile(rule));

/**
 * Matches when [rule] does not match.  No input is consumed.
 *
 * NOT does not generate a value.
 */
_Rule NOT(rule) => new _NegativeContextRule(_compile(rule));

/**
 * Matches [rule] but generates no value even if [rule] generates a value.
 *
 * SKIP never generates a value.
 */
_Rule SKIP(rule) => new _SkipRule(_compile(rule));

/**
 * Matches [rule] in a lexical context where whitespace is not automatically
 * skipped.  Useful for matching what would normally be considered to be tokens.
 * [name] is a user-friendly description of what is being matched and is used in
 * error messages.
 *
 * LEX(rule)
 * LEX(name, rule)
 *
 * LEX does not generate a value.  If a value is required, wrap LEX with TEXT.
 */
_Rule LEX(arg1, [arg2]) {
  if (arg2 == null)
    return new _LexicalRule(arg1 is String ? arg1 : null, _compile(arg1));
  else
    return new _LexicalRule(arg1, _compile(arg2));
}

/**
 * Matches [rule] and generates a value from the matched text. If the [rule]
 * matches, then TEXT(rule) matches and has a value derived from the string
 * fragment that was matched.  The default derived value is the string fragment.
 *
 * TEXT always generates a value.
 */
_Rule TEXT(rule, [extractor]) => new _TextValueRule(
    _compile(rule),
    extractor == null
        ? (string, start, end) => string.substring(start, end)
        : extractor);

/**
 * Matches an optional rule.
 *
 * MAYBE is a value generating matcher.
 *
 * If [rule] is value generating then the value is the value generated by [rule]
 * if it matches, and [:null:] if it does not.
 *
 * If [rule] is not value generating then the value is [:true:] if [rule]
 * matches and [:false:] if it does not.
 */
_Rule MAYBE(rule) => new _OptionalRule(_compile(rule));

/**
 * MANY(rule) matches [rule] [min] or more times.
 * [min] must be 0 or 1.
 * If [separator] is provided it is used to match a separator between matches of
 * [rule].
 *
 * MANY is a value generating matcher.  The value is a list of the matches of
 * [rule].  The list may be empty if [:min == 0:].
 */
_Rule MANY(rule, {separator: null, int min: 1}) {
  assert(0 <= min && min <= 1);
  return new _RepeatRule(_compile(rule), _compileOptional(separator), min);
}

/**
 * Matches [rule] zero or more times.  Shorthand for [:MANY(rule, min:0):]
 * TODO: retire min: parameter?
 *
 * MANY0 is a value generating matcher.
 */
_Rule MANY0(rule, [separator = null]) {
  return new _RepeatRule(_compile(rule), _compileOptional(separator), 0);
}

/**
 * Matches [rules] in order until one succeeds.
 *
 * OR is value-generating.
 */
_Rule OR(
        [a,
        b,
        c,
        d,
        e,
        f,
        g,
        h,
        i,
        j,
        k,
        l,
        m,
        n,
        o,
        p,
        q,
        r,
        s,
        t,
        u,
        v,
        w,
        x,
        y,
        z]) =>
    _compileMultiRule(
        (a is List && b == null) // Backward compat. OR([a, b]) => OR(a, b).
            ? a
            : _unspread(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s,
                t, u, v, w, x, y, z),
        false,
        (compiledRules, valueCount, reducer) => new _ChoiceRule(compiledRules));

_Rule SEQ(
        [a,
        b,
        c,
        d,
        e,
        f,
        g,
        h,
        i,
        j,
        k,
        l,
        m,
        n,
        o,
        p,
        q,
        r,
        s,
        t,
        u,
        v,
        w,
        x,
        y,
        z]) =>
    _compile(_unspread(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s,
        t, u, v, w, x, y, z));

/**
 * Matches [rule]
 */
_Rule MEMO(rule) => new _MemoRule(_compile(rule));

_Rule TAG(tag, rule) => _compile([
      rule,
      (ast) => [tag, ast]
    ]);

class ParseError implements Exception {
  const ParseError(String this._message);
  String toString() => _message;
  final String _message;
}

/**
 * A grammar is a collection of symbols and rules that may be used to parse an
 * input.
 */
class Grammar {
  Map<String, Symbol> _symbols;

  /** This rule may be set by the user to define whitespace. */
  _Rule _whitespace;

  _Rule get whitespace => _whitespace;
  void set whitespace(rule) {
    _whitespace = _compile(rule);
  }

  Grammar() {
    _symbols = new Map<String, Symbol>();
    whitespace = CHAR(' \t\r\n');
  }

  /**
   * operator [] is used to find or create symbols. Symbols may appear in rules
   * to define recursive rules.
   */
  Symbol operator [](String name) {
    if (_symbols.containsKey(name)) return _symbols[name];
    Symbol s = new Symbol(name, this);
    _symbols[name] = s;
    return s;
  }

  /**
   * Parses the input string and returns the parsed AST, or throws an exception
   * if the input can't be parsed.
   */
  parse(root, String text) {
    for (var symbol in _symbols.values)
      if (symbol._rule == null) print('${symbol.name} is undefined');

    var state = new _ParserState(text, whitespace: whitespace);
    var match = _compile(root).match(state, 0);
    if (match == null) return diagnose(state);
    var pos = match[0];
    pos = _skip_whitespace(state, pos);
    if (pos == state._end) return match[1];
    // TODO: Make this complain about expecting end of file.
    return diagnose(state);
  }

  diagnose(state) {
    var message = 'unexpected error';
    if (!state.max_rule.isEmpty) {
      var s = new Set();
      for (var rule in state.max_rule) s.add(rule.description());
      var tokens = new List<String>.from(s);
      tokens.sort((a, b) => a.startsWith("'") == b.startsWith("'")
          ? a.compareTo(b)
          : a.startsWith("'") ? 1 : -1);
      var expected = tokens.join(' or ');
      var found = state.max_pos == state._end
          ? 'end of file'
          : "'${state._text[state.max_pos]}'";
      message = 'Expected $expected but found $found';
    }
    int start = state.max_pos;
    int end = start;
    while (start >= 1 && state._text[start - 1] != '\n') --start;
    while (end < state._text.length && state._text[end] != '\n') ++end;
    var line = state._text.substring(start, end);
    var indicator = '';
    for (var i = 0; i < line.length && start + i < state.max_pos; i++)
      indicator = ' $indicator';
    indicator = '$indicator^';
    // TODO: Convert to an exception.
    print(message);
    print(line);
    print(indicator);
    return null;
  }
}

class Symbol {
  final String name;
  final Grammar grammar;
  _Rule _rule;

  Symbol(this.name, this.grammar);

  void set def(rule) {
    assert(_rule == null); // Assign once.
    _rule = _compile(rule);
  }

  toString() => _rule == null ? '<$name>' : '<$name = $_rule>';
}

class _ParserState {
  _ParserState(this._text, {_Rule whitespace}) {
    _end = this._text.length;
    whitespaceRule = whitespace;
    max_rule = [];
  }

  String _text;
  int _end;

  //
  bool inWhitespaceMode = false;
  _Rule whitespaceRule = null;

  // Used for constructing an error message.
  int inhibitExpectedTrackingDepth = 0;
  int max_pos = 0;
  var max_rule;
}

/**
 * An interface tag for rules. If this tag is on a rule, then the description()
 * of the rule is something sensible to put in a message.
 */
abstract class _Expectable {
  String description();
}

class _Rule {
  const _Rule();
  // Returns null for a match failure or [pos, ast] for success.
  match(_ParserState state, int pos) {
    if (!state.inWhitespaceMode) {
      pos = _skip_whitespace(state, pos);
    }
    return matchAfterWS(state, pos);
  }

  // Faster entry point for matching a sub-rule that is matched to the start
  // position of the super-rule.  Whitespace has already been skipped so no need
  // to try to skip it again.
  matchAfterWS(_ParserState state, int pos) {
    if (state.inhibitExpectedTrackingDepth == 0) {
      // Track position for possible error messaging
      if (pos > state.max_pos) {
        // Store position and the rule.
        state.max_pos = pos;
        if (this is _Expectable) {
          state.max_rule = [this];
        } else {
          state.max_rule = [];
        }
      } else if (pos == state.max_pos) {
        if (this is _Expectable) {
          state.max_rule.add(this);
        }
      }
    }
    // Delegate the matching logic to the specialized function.
    return _match(state, pos);
  }

  // Overridden in subclasses to match the rule.
  _match(_ParserState state, int pos) => null;

  // Does the rule generate a value (AST) with the match?
  bool get generatesValue => false;

  get defaultValue => null;
}

int _skip_whitespace(state, pos) {
  // Returns the next non-whitespace position.
  // This is done by matching the optional whitespaceRule with the current text.
  if (state.whitespaceRule == null) return pos;
  state.inWhitespaceMode = true;
  state.inhibitExpectedTrackingDepth++;
  while (true) {
    var match = state.whitespaceRule.match(state, pos);
    if (match == null) break;
    pos = match[0];
  }
  state.inWhitespaceMode = false;
  state.inhibitExpectedTrackingDepth--;
  return pos;
}

_Rule _compileOptional(rule) {
  return rule == null ? null : _compile(rule);
}

_Rule _compile(rule) {
  if (rule is _Rule) return rule;
  if (rule is String) return new _StringRule(rule);
  if (rule is Symbol) return new _SymbolRule(rule);
  if (rule is RegExp) return new _RegExpRule(rule);
  if (rule is List) {
    return _compileMultiRule(
        rule,
        true,
        (compiledRules, valueCount, reducer) =>
            new _SequenceRule(compiledRules, valueCount, reducer));
  }
  throw new Exception('Cannot compile rule: $rule');
}

class _EndOfInputRule extends _Rule {
  _match(_ParserState state, int pos) {
    if (pos == state._end) return [pos, null];
    return null;
  }

  toString() => 'END';
}

class _ErrorRule extends _Rule {
  String message;
  _ErrorRule(String this.message);
  _match(_ParserState state, int pos) {
    throw new ParseError(message);
  }

  toString() => 'ERROR($message)';
}

class _CharCodeRule extends _Rule {
  Function _predicate;
  var _name;
  _CharCodeRule(this._predicate, this._name);
  _match(_ParserState state, int pos) {
    if (pos == state._end) return null;
    int code = state._text.codeUnitAt(pos);
    if (_predicate(code)) return [pos + 1, null];
    return null;
  }

  toString() => _name == null ? 'CHARCODE($_predicate)' : 'CHARCODE($_name)';
}

class _AnyCharRule extends _Rule {
  const _AnyCharRule();
  _match(_ParserState state, int pos) {
    if (pos == state._end) return null;
    return [pos + 1, null];
  }

  toString() => 'CHAR()';
}

class _SymbolRule extends _Rule {
  final Symbol _symbol;
  _SymbolRule(Symbol this._symbol);
  _match(_ParserState state, int pos) {
    if (_symbol._rule == null)
      throw new Exception("Symbol '${_symbol.name}' is undefined");
    return _symbol._rule.match(state, pos);
  }

  bool get generatesValue => true;

  toString() => '<${_symbol.name}>';
}

class _SkipRule extends _Rule {
  // A rule that has no value.
  _Rule _rule;
  _SkipRule(_Rule this._rule);
  _match(_ParserState state, int pos) {
    var match = _rule.matchAfterWS(state, pos);
    if (match == null) return null;
    return [match[0], null];
  }

  toString() => 'TOKEN($_rule)';
}

class _StringRule extends _Rule implements _Expectable {
  final String _string;
  int _len;
  _StringRule(this._string) {
    _len = _string.length;
  }

  _match(_ParserState state, int pos) {
    if (pos + _len > state._end) return null;
    for (int i = 0; i < _len; i++) {
      if (state._text.codeUnitAt(pos + i) != _string.codeUnitAt(i)) return null;
    }
    return [pos + _len, null];
  }

  //get defaultValue => _string;

  toString() => '"$_string"';

  description() => "'$_string'";
}

class _RegExpRule extends _Rule {
  RegExp _re;
  _RegExpRule(this._re) {
    // There is no convenient way to match an anchored substring.
    throw new Exception('RegExp matching not supported');
  }

  toString() => '"$_re"';
}

class _LexicalRule extends _Rule implements _Expectable {
  final String _name;
  final _Rule _rule;

  _LexicalRule(String this._name, _Rule this._rule);

  _match(_ParserState state, int pos) {
    state.inWhitespaceMode = true;
    state.inhibitExpectedTrackingDepth++;
    var match = _rule.matchAfterWS(state, pos);
    state.inhibitExpectedTrackingDepth--;
    state.inWhitespaceMode = false;
    return match;
  }

  toString() => _name;

  description() => _name == null ? '?' : _name;
}

class _TextValueRule extends _Rule {
  final _Rule _rule;
  final _extract; // Function

  _TextValueRule(_Rule this._rule, Function this._extract);

  _match(_ParserState state, int pos) {
    var match = _rule.matchAfterWS(state, pos);
    if (match == null) {
      return null;
    }
    var endPos = match[0];
    return [endPos, _extract(state._text, pos, endPos)];
  }

  bool get generatesValue => true;

  toString() => 'TEXT($_rule)';
}

_Rule _compileMultiRule(
    List rules, bool allowReducer, finish(compiledRules, valueCount, reducer)) {
  int valueCount = 0;
  List compiledRules = new List<_Rule>();
  Function reducer;
  for (var rule in rules) {
    if (reducer != null)
      throw new Exception('Reducer must be last in sequence: $rule');
    if (rule is Function) {
      if (allowReducer)
        reducer = rule;
      else
        throw new Exception('Bad rule: "$rule"');
    } else {
      _Rule compiledRule = _compile(rule);
      if (compiledRule.generatesValue) ++valueCount;
      compiledRules.add(compiledRule);
    }
  }
  return finish(compiledRules, valueCount, reducer);
}

String _formatMultiRule(String functor, List rules) {
  var sb = new StringBuffer(functor);
  sb.write('(');
  var separator = '';
  for (var rule in rules) {
    sb.write(separator);
    sb.write(rule);
    separator = ',';
  }
  sb.write(')');
  return sb.toString();
}

class _SequenceRule extends _Rule {
  // This rule matches the component rules in order.
  final List<_Rule> _rules;
  final int _generatingSubRules;
  final Function _reducer;
  bool _generatesValue;
  _SequenceRule(List<_Rule> this._rules, int this._generatingSubRules,
      Function this._reducer) {
    _generatesValue = _generatingSubRules > 0 || _reducer != null;
  }

  _match(state, pos) {
    var sequence = [];
    for (var rule in _rules) {
      var match = rule.match(state, pos);
      if (match == null) return null;
      if (rule.generatesValue) {
        var ast = match[1];
        sequence.add(ast);
      }
      pos = match[0];
    }
    if (_reducer == null) {
      if (_generatingSubRules == 0) return [pos, null];
      if (_generatingSubRules == 1) return [pos, sequence[0]];
      return [pos, sequence];
    } else {
      return [pos, _apply(_reducer, sequence)];
    }
  }

  bool get generatesValue => _generatesValue;

  toString() => _formatMultiRule('SEQ', _rules);
}

class _ChoiceRule extends _Rule {
  // This rule matches the first component rule that matches.
  List<_Rule> _rules;
  _ChoiceRule(List<_Rule> this._rules);

  _match(state, pos) {
    for (var rule in _rules) {
      var match = rule.match(state, pos);
      if (match != null) {
        /*
        if (!rule.generatesValue) {
          var value = rule.defaultValue;
          if (value != null)
            return [match[0], value];
        }
        */
        return match;
      }
    }
    return null;
  }

  bool get generatesValue => true;

  toString() => _formatMultiRule('OR', _rules);
}

class _OptionalRule extends _Rule {
  _Rule _rule;
  _OptionalRule(_Rule this._rule);
  _match(_ParserState state, int pos) {
    var match = _rule.match(state, pos);
    if (_rule.generatesValue) return match == null ? [pos, null] : match;
    return match == null ? [pos, false] : [match[0], true];
  }

  bool get generatesValue => true;

  toString() => 'MAYBE($_rule)';
}

class _ContextRule extends _Rule {
  _Rule _rule;
  _ContextRule(_Rule this._rule);
  _match(_ParserState state, int pos) {
    // TODO: protect error state.
    var match = _rule._match(state, pos);
    if (match == null) return null;
    return [pos, null];
  }

  toString() => 'AT($_rule)';
}

class _NegativeContextRule extends _Rule {
  _Rule _rule;
  _NegativeContextRule(_Rule this._rule);
  _match(_ParserState state, int pos) {
    // TODO: protect error state.
    var match = _rule._match(state, pos);
    if (match == null) return [pos, null];
    return null;
  }

  toString() => 'NOT($_rule)';
}

class _RepeatRule extends _Rule {
  // Matches zero, one or more items.
  _Rule _rule;
  _Rule _separator;
  int _min;

  _RepeatRule(this._rule, this._separator, this._min);

  _match(state, pos) {
    // First match.
    var match = _rule.match(state, pos);
    if (match == null) if (_min == 0)
      return [pos, []];
    else
      return null;
    pos = match[0];
    var result = [match[1]];

    // Subsequent matches:
    while (true) {
      var newPos = pos;
      if (_separator != null) {
        match = _separator.match(state, pos);
        if (match == null) return [pos, result];
        newPos = match[0];
      }
      match = _rule.match(state, newPos);
      if (match == null) return [pos, result];
      pos = match[0];
      result.add(match[1]);
    }
  }

  bool get generatesValue => true;

  toString() =>
      'MANY(min:$_min, $_rule${_separator==null?'':", sep: $_separator"})';
}

class _MemoRule extends _Rule {
  final _Rule _rule;

  var parseInstance;

  // A map from position to result.  Can this be replaced with something
  // smaller?
  // TODO: figure out how to discard the map and parseInstance after parsing.
  Map<int, Object> map;

  _MemoRule(this._rule);

  _match(state, pos) {
    // See if we are still parsing the same input.  Relies on the fact that the
    // input is a string and strings are immutable.
    if (!identical(parseInstance, state._text)) {
      map = new Map<int, Object>();
      parseInstance = state._text;
    }
    // TODO: does this have to check or preserve parse state (like
    // inWhitespaceMode, error position info etc?)
    // Stored result can be null (memoized failure).
    if (map.containsKey(pos)) {
      return map[pos];
    }
    var match = _rule.match(state, pos);
    map[pos] = match;
    return match;
  }

  bool get generatesValue => _rule.generatesValue;

  toString() => 'MEMO($_rule)';
}

_apply(fn, List args) {
  switch (args.length) {
    case 0:
      return fn();
    case 1:
      return fn(args[0]);
    case 2:
      return fn(args[0], args[1]);
    case 3:
      return fn(args[0], args[1], args[2]);
    case 4:
      return fn(args[0], args[1], args[2], args[3]);
    case 5:
      return fn(args[0], args[1], args[2], args[3], args[4]);
    case 6:
      return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
    case 7:
      return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
    case 8:
      return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6],
          args[7]);
    case 9:
      return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6],
          args[7], args[8]);
    case 10:
      return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6],
          args[7], args[8], args[9]);

    default:
      throw new Exception('Too many arguments in _apply: $args');
  }
}

List _unspread(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v,
    w, x, y, z) {
  List list = new List();
  add(element) {
    if (element != null) list.add(element);
  }

  add(a);
  add(b);
  add(c);
  add(d);
  add(e);
  add(f);
  add(g);
  add(h);
  add(i);
  add(j);
  add(k);
  add(l);
  add(m);
  add(n);
  add(o);
  add(p);
  add(q);
  add(r);
  add(s);
  add(t);
  add(u);
  add(v);
  add(w);
  add(x);
  add(y);
  add(z);
  return list;
}
